Profile picture

[k8s] 쿠버네티스로 Django 웹 서비스 배포하기 (NFS, pv-pvc, ConfigMap)

JaehyoJJAng2023년 06월 10일

개요

쿠버네티스로 Pinterest 기반의 웹 서비스를 배포해보려고 한다.

여기서 Pod 간 NFS 연동, PV-PVC, ConfigMap 등을 종합적으로 사용해볼 것이다.


프로젝트 구조

Django 개발 서버 디렉토리 구조

├── apps
│   ├── accountapp
│   ├── articleapp
│   ├── commentapp
│   ├── dislikeapp
│   ├── Dockerfile
│   ├── likeapp
│   ├── manage.py
│   ├── media
│   ├── pragmatic
│   ├── profileapp
│   ├── projectapp
│   ├── requirements.txt
│   ├── static
│   ├── staticfiles
│   ├── subscribeapp
│   └── templates
├── configMap
│   └── django-config.yaml
├── k8s-db-deployment.yaml
├── k8s-django-deployment.yaml
├── k8s-nginx-deployment.yaml
└── pv-pvc
    ├── db-pv-pvc.yaml
    ├── nginx-media-pv-pvc.yaml
    ├── nginx-static-pv-pvc.yaml
    ├── web-media-pv-pvc.yaml
    └── web-static-pv-pvc.yaml

사전 준비

  • 외부로 배포하기 위해 Nginx Ingress Controller & metalLB 설치 필요

postgresql DB 접속을 위해 django의 settings.py을 아래와 같이 수정

...

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': os.getenv('POSTGRES_DB','postgres'),
        'USER': os.getenv('POSTGRES_USER','postgres'),
        'PASSWORD': os.getenv('POSTGRES_PASSWORD','postgres'),
        'HOST': os.getenv('POSTGRES_HOST','postgres-service'),
        'PORT': int(os.getenv('POSTGRES_PORT',5432))
    }
}
...

프로젝트 생성

  • 해당 섹션의 하위 작업들은 모두 배포 서버(k8s)가 아닌 개발 서버에서 진행

Django 프로젝트 생성

# 가상환경 활성화
pyenv virtualenv 3.11.6 py3_11_6
pyenv activate py3_11_6

# Django 설치
pip install django

# 장고 프로젝트 생성
django-admin startproject myapp .

도커 이미지 생성 & 빌드

  • 개발한 Django 앱을 Dockerhub에 배포
  • postgresql은 빌드된 이미지 없이 순정 이미지를 pull 받아서 띄울 것임.

django

Dockerfile 작성

# pull official base image
FROM python:3.8-slim-buster

# set work directory
WORKDIR /usr/src/app

# set environment variable
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONBUFFERED 1

COPY . .

# install dependencies
RUN pip install --upgrade pip && pip install -r requirements.txt
RUN python manage.py migrate
CMD ["gunicorn", "pragmatic.wsgi:application", "--bind", "0.0.0.0:8000", "--reload"]

이미지 배포

  • 이미지 registry 서버를 따로 구축하여 해당 서버로부터 이미지를 pull 받아오도록 하였음.

이미지 빌드

docker build --tag xxxx/k8s-pinterest .

레지스트리 서버로 이미지 배포 위해 이미지 태그 변경

docker tag xxxx/k8s-pinterest <registry_서버_IP>:5000/k8s-pinterest

레지스트리 서버로 이미지 배포

docker push <registry_서버_IP>:5000/k8s-pinterest

서비스 배포하기

  • 해당 섹션의 하위 작업들은 쿠버네티스 마스터 노드에서 진행

PV 및 PVC 생성

Django, PostgreSQL, nginx 데이터를 위한 스토리지를 설정하자.

이 때 PV는 NFS로 설정한다.


참고로 nginx의 PV-PVC를 생성하는 이유는 다음과 같다.

  • Django 앱의 staticfiles, media에 대한 파일 서빙이 필요함.

코드를 작성해보자.


DB용: db-pv-pvc.yaml

apiVersion: v1
kind: PersistentVolume
metadata:
  name: db-pv
spec:
  capacity:
    storage: 10Gi
  accessModes:
    - ReadWriteOnce
  nfs:
    server: 192.168.219.179
    path: /nfs/share/db
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: db-pvc
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi

APP용: app-pv-pvc.yaml

apiVersion: v1
kind: PersistentVolume
metadata:
  name: web-static-pv
spec:
  capacity:
    storage: 5Gi
  accessModes:
    - ReadWriteMany
  nfs:
    server: 192.168.219.179
    path: /nfs/share/web/staticfiles
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: web-static-pvc
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 5Gi
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: web-media-pv
spec:
  capacity:
    storage: 5Gi
  accessModes:
    - ReadWriteMany
  nfs:
    server: 192.168.219.179
    path: /nfs/share/web/media
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: web-media-pvc
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 5Gi

ConfigMap 생성

Django와 PostgreSQL의 환경변수를 설정하자.

configmap.yaml

apiVersion: v1
kind: ConfigMap
metadata:
  name: django-config 
data:
  DEBUG: "1"
  SECRET_KEY: "XX"
  DJANGO_ALLOWED_HOSTS: "localhost 127.0.0.1 192.168.219.0/24 [::1]"
  SQL_ENGINE: "django.db.backends.postgresql"
  SQL_DATABASE: "pragmatic"
  SQL_USER: "pragmatic_user"
  SQL_PASSWORD: "pragmatic_pass"
  SQL_HOST: "db"
  SQL_PORT: "5432"
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: postgres-config 
data:
  POSTGRES_USER: "pragmatic_user"
  POSTGRES_PASSWORD: "pragmatic_pass"
  POSTGRES_DB: "pragmatic"

PostgreSQL Deployment 및 Service 생성

PostgreSQL 데이터베이스 설정이다.

k8s-db-deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: postgres
spec:
  replicas: 1
  selector:
    matchLabels:
      app: postgres
  template:
    metadata:
      labels:
        app: postgres
    spec:
      containers:
        - name: postgres
          image: postgres:12.0-alpine
          ports:
            - containerPort: 5432
          env:
            - name: POSTGRES_USER
              valueFrom:
                configMapKeyRef:
                  name: postgres-config
                  key: POSTGRES_USER
            - name: POSTGRES_PASSWORD
              valueFrom:
                configMapKeyRef:
                  name: postgres-config
                  key: POSTGRES_PASSWORD
            - name: POSTGRES_DB
              valueFrom:
                configMapKeyRef:
                  name: postgres-config
                  key: POSTGRES_DB
          volumeMounts:
            - name: db-data
              mountPath: /var/lib/postgresql/data
      volumes:  # This section must align properly under 'spec'
        - name: db-data
          persistentVolumeClaim:
            claimName: db-pvc
---
apiVersion: v1
kind: Service
metadata:
  name: db
spec:
  selector:
    app: postgres
  ports:
    - protocol: TCP
      port: 5432
      targetPort: 5432
  clusterIP: None # StatefulSets와 비슷하게 Pod 간 직접 통신 지원

Django Deployment 및 Service 생성

Django 애플리케이션 설정이다.

k8s-app-deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: django
spec:
  replicas: 2
  selector:
    matchLabels:
      app: django
  template:
    metadata:
      labels:
        app: django
    spec:
      containers:
        - name: django
          image: yshrim12/pinterest
          ports:
            - containerPort: 8000
          env:
            - name: DEBUG
              valueFrom:
                configMapKeyRef:
                  name: django-config
                  key: DEBUG
            - name: SECRET_KEY
              valueFrom:
                configMapKeyRef:
                  name: django-config
                  key: SECRET_KEY
            - name: DJANGO_ALLOWED_HOSTS
              valueFrom:
                configMapKeyRef:
                  name: django-config
                  key: DJANGO_ALLOWED_HOSTS
            - name: SQL_ENGINE
              valueFrom:
                configMapKeyRef:
                  name: django-config
                  key: SQL_ENGINE
            - name: SQL_DATABASE
              valueFrom:
                configMapKeyRef:
                  name: django-config
                  key: SQL_DATABASE
            - name: SQL_USER
              valueFrom:
                configMapKeyRef:
                  name: django-config
                  key: SQL_USER
            - name: SQL_PASSWORD
              valueFrom:
                configMapKeyRef:
                  name: django-config
                  key: SQL_PASSWORD
            - name: SQL_HOST
              valueFrom:
                configMapKeyRef:
                  name: django-config
                  key: SQL_HOST
            - name: SQL_PORT
              valueFrom:
                configMapKeyRef:
                  name: django-config
                  key: SQL_PORT 
          volumeMounts:
            - name: static-data
              mountPath: /usr/src/app/staticfiles
            - name: media-data
              mountPath: /usr/src/app/media
      volumes:
        - name: static-data
          persistentVolumeClaim:
            claimName: web-static-pvc
        - name: media-data
          persistentVolumeClaim:
            claimName: web-media-pvc
---
apiVersion: v1
kind: Service
metadata:
  name: web
spec:
  selector:
    app: django
  ports:
    - protocol: TCP
      port: 8000
      targetPort: 8000

Nginx Deployment 및 Service 생성

Nginx 설정이다.

k8s-nginx-deployment.yaml

# nginx.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:latest
        ports:
        - containerPort: 80
        volumeMounts:
        - name: nginx-config
          mountPath: /etc/nginx/conf.d/default.conf
          subPath: nginx.conf
        - name: static-data
          mountPath: /usr/src/app/staticfiles
        - name: media-data
          mountPath: /usr/src/app/media
      volumes:
      - name: nginx-config
        configMap:
          name: nginx-config
      - name: static-data
        persistentVolumeClaim:
          claimName: web-static-pvc
      - name: media-data
        persistentVolumeClaim:
          claimName: web-media-pvc
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: nginx-config
data:
  nginx.conf: |
    upstream pragmatic {
        server web:8000;
    }

    server {
        listen 80;
        client_max_body_size 0;

        location / {
            proxy_pass http://pragmatic;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header Host $host;
            proxy_redirect off;
        }

        location /static/ {
            alias /usr/src/app/staticfiles/;
        }

        location /media/ {
            alias /usr/src/app/media/;
        }
    }
---
apiVersion: v1
kind: Service
metadata:
  name: nginx
spec:
  selector:
    app: nginx
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80
    nodePort: 30001
  type: NodePort

nginx에서도 web-static-pvcweb-media-pvc를 사용하도록 하자.



data:
  nginx.conf: |
    upstream pragmatic {
        server web:8000;
    }

    server {
        listen 80;
        client_max_body_size 0;

        location / {
            proxy_pass http://pragmatic;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header Host $host;
            proxy_redirect off;
        }

        location /static/ {
            alias /usr/src/app/staticfiles/;
        }

        location /media/ {
            alias /usr/src/app/media/;
        }
    }
---

nginx에서 django 앱의 /static/, /media/ 경로에 대해 정적 파일 서빙을 해주기 위해서는

django 파드 내에 있는 /usr/src/app/staticfiles, /usr/src/app/media 경로에 파일들이 필요한데

이때 nginx에서도 web-static-pvcweb-media-pvc를 사용하여

NFS 서버를 통해 해당 파일들을 nginx 파드로 넘겨줄 수 있도록 하는거다.


그리고 data.nginx.conf의 설정 값 중에,

upstream 섹션의 server web:8000; 이 부분은 django 앱의 서비스 명세서 이름을 지정한 것이다.

이는 쿠버네티스의 서비스 디스커버리(Service Discovery)에 대해 알고 있다면 무슨 뜻인지 이해가 갈 것이다.


리소스 적용

모든 YAML 파일을 클러스터에 적용하자.

# ConfigMap 적용
kubectl apply -f ./configmap

# pv-pvc 적용
kubectl apply -f ./pv-pvc

# deployment 적용
kubectl apply -f .

여기서 ./configmap은 configmap이 모여있는 경로이고,

./pv-pvc는 pv-pvc 명세서가 모여있는 경로이다.


접속 테스트

image


인그레스(Ingress) 적용

Ingress 실습 & helm으로 ingress-nginx-controller 설치하기 참고하기


트러블슈팅

watch 명령으로 Pod 간 STATUS에 이상이 없는지 모니터링

watch kubectl get pods -o wide

Pod 로그/이벤트 조회

kubectl describe pod/<pod_name>

413 Request entity too large

nginx-ingress에서 413 Request entity too large 에러 발생.
413 Request entity too large 해결하기 - k8s/ingress


Loading script...